package com.maple.visit;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.druid.DbType;
import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.expr.*;
import com.alibaba.druid.sql.ast.statement.*;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlInsertStatement;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlSelectQueryBlock;
import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlASTVisitorAdapter;
import com.alibaba.druid.util.JdbcConstants;
import com.alibaba.druid.util.StringUtils;
import com.maple.config.FieldConditionConfig;
import com.maple.config.TableInfo;
import lombok.AllArgsConstructor;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * Sql条件工具
 *
 * @author maple
 * @version 1.0
 * @since 2020-11-27 00:25
 */
@AllArgsConstructor
public class SqlConditionJoinVisitor extends MySqlASTVisitorAdapter {
    private static final DbType DB_TYPE = JdbcConstants.MYSQL;
    private final List<FieldConditionConfig> fieldConditionConfigList;

    @Override
    public boolean visit(SQLSelectStatement x) {
        SQLSelect sqlSelect = x.getSelect();
        // union查询
        Optional.ofNullable(sqlSelect.getQuery()).ifPresent(item -> item.accept(this));

        // 普通查询
        Optional.ofNullable(sqlSelect.getQueryBlock()).ifPresent(item -> item.accept(this));
        return super.visit(x);
    }

    @Override
    public boolean visit(MySqlInsertStatement x) {
        for (FieldConditionConfig config : fieldConditionConfigList) {
            if (!config.isWhiteTable(x.getTableName().getSimpleName())) {
                continue;
            }
            List<SQLExpr> exprList = x.getColumns();
            SQLIdentifierExpr sqlIdentifierExpr = new SQLIdentifierExpr(config.getFieldName());
            // 存在此字段则忽略
            if (exprList.contains(sqlIdentifierExpr)) {
                continue;
            }
            // 加入插入字段和默认值
            exprList.add(sqlIdentifierExpr);

            // 如果为空 则可能为子查询 如果子查询的条件不足 则加上
            if (CollUtil.isEmpty(x.getValuesList()) && Objects.nonNull(x.getQuery())
                    && x.getQuery().getQuery() instanceof MySqlSelectQueryBlock) {
                List<SQLSelectItem> childSelectList = ((MySqlSelectQueryBlock) x.getQuery().getQuery()).getSelectList();
                if (childSelectList.size() < exprList.size()) {
                    SQLSelectItem sqlSelectItem = new SQLSelectItem();
                    sqlSelectItem.setExpr(config.getValueSqlExpr());
                    childSelectList.add(sqlSelectItem);
                }
                return super.visit(x);
            }
            x.getValuesList().forEach(valueClause -> valueClause.addValue(config.getValueSqlExpr()));
        }
        return super.visit(x);
    }

    @Override
    public boolean visit(MySqlSelectQueryBlock selectQueryBlock) {
        SQLExpr where = selectQueryBlock.getWhere();
        // 处理查询字段中的子查询
        selectQueryBlock.getSelectList().forEach(item -> item.accept(this));

        // 获取并处理from块的查询
        SQLTableSource from = selectQueryBlock.getFrom();
        Optional.ofNullable(from).ifPresent(item -> item.accept(this));

        // 替换where
        selectQueryBlock.setWhere(joinAllFieldAndReturnNewWhere(where, from));
        return super.visit(selectQueryBlock);
    }

    /**
     * 拼接所有字段的where条件并返回新where
     *
     * @param where       原始where
     * @param tableSource 表信息
     * @return 新where
     */
    private SQLExpr joinAllFieldAndReturnNewWhere(SQLExpr where, SQLTableSource tableSource) {
        List<TableInfo> tableInfoList = new ArrayList<>();
        // 获取当前层级的表名和别名信息
        getTableInfo(tableSource, tableInfoList);
        SQLExpr rsCondition = where;

        if (CollUtil.isEmpty(tableInfoList)) {
            return where;
        }

        for (FieldConditionConfig config : fieldConditionConfigList) {
            // 过滤白名单
            List<TableInfo> finalList =
                    tableInfoList.stream().filter(item -> Objects.nonNull(item.getTableName()) && config.isWhiteTable(item.getTableName())).collect(Collectors.toList());

            // 过滤已经手动指定字段的表
            filterCustomColumnTable(where, finalList, config);

            if (CollUtil.isEmpty(finalList)) {
                continue;
            }

            rsCondition = getNewWhereCondition(rsCondition, finalList, config);
        }

        return rsCondition;
    }

    /**
     * 获取新where条件
     *
     * @param where where
     * @return 新where
     */
    private SQLExpr getNewWhereCondition(SQLExpr where, List<TableInfo> tableInfoList, FieldConditionConfig config) {
        // 处理where中所有子查询
        doWhereSubQuery(where);

        // 根据表信息拼接条件
        SQLBinaryOpExpr conditionSqlByTableInfo = joinConditionSqlByTableList(tableInfoList, config);

        if (Objects.isNull(where)) {
            return conditionSqlByTableInfo;
        }

        return and(where.clone(), conditionSqlByTableInfo);
    }

    /**
     * 获取同级所有表信息
     *
     * @param tableSource 表源对象
     */
    private void getTableInfo(SQLTableSource tableSource, List<TableInfo> tableInfoList) {
        if (tableSource instanceof SQLSubqueryTableSource) {
            tableInfoList.add(new TableInfo(null, tableSource.getAlias()));
        }

        if (tableSource instanceof SQLJoinTableSource) {
            SQLJoinTableSource joinSource = (SQLJoinTableSource) tableSource;
            getTableInfo(joinSource.getLeft(), tableInfoList);
            getTableInfo(joinSource.getRight(), tableInfoList);
        }

        if (tableSource instanceof SQLExprTableSource) {
            tableInfoList.add(new TableInfo(String.valueOf(tableSource), tableSource.getAlias()));
        }
    }

    private void doWhereSubQuery(SQLExpr where) {
        if (Objects.isNull(where)) {
            return;
        }

        // 遍历左右两端
        SQLExpr sqlExpr = where;
        while (sqlExpr instanceof SQLBinaryOpExpr && Objects.nonNull(sqlExpr = ((SQLBinaryOpExpr) sqlExpr).getLeft())) {
            doWhereSubQuery(sqlExpr);
        }

        sqlExpr = where;
        while (sqlExpr instanceof SQLBinaryOpExpr && Objects.nonNull(sqlExpr = ((SQLBinaryOpExpr) sqlExpr).getRight())) {
            doWhereSubQuery(sqlExpr);
        }

        if (!(where instanceof SQLInSubQueryExpr)) {
            return;
        }

        // 处理子查询
        ((SQLInSubQueryExpr) where).getSubQuery().getQueryBlock().accept(this);
    }

    // 过滤已经自定义了字段的表信息
    private void filterCustomColumnTable(SQLExpr where, List<TableInfo> tableInfoList, FieldConditionConfig config) {
        if (Objects.isNull(where)) {
            return;
        }

        // 遍历左右两端
        SQLExpr sqlExpr = where;
        while (sqlExpr instanceof SQLBinaryOpExpr && Objects.nonNull(sqlExpr = ((SQLBinaryOpExpr) sqlExpr).getLeft())) {
            filterCustomColumnTable(sqlExpr, tableInfoList, config);
        }

        sqlExpr = where;
        while (sqlExpr instanceof SQLBinaryOpExpr && Objects.nonNull(sqlExpr = ((SQLBinaryOpExpr) sqlExpr).getRight())) {
            filterCustomColumnTable(sqlExpr, tableInfoList, config);
        }

        // 未设置别名 则直接判断是否当前字段
        if (where instanceof SQLInSubQueryExpr) {
            filterCustomColumnTable(((SQLInSubQueryExpr) where).getExpr(), tableInfoList, config);
        }
        // 处理形如 A = 1 存在字段名相同的条件则去掉
        if (where instanceof SQLIdentifierExpr) {
            tableInfoList.removeIf(item -> Objects.isNull(item.getAlias()) && Objects.equals(config.getFieldName(),
                    ((SQLIdentifierExpr) where).getName()));
            return;
        }

        // 处理形如 a.A = 1 存在别名和字段名都相同的条件则去掉
        if (where instanceof SQLPropertyExpr) {
            SQLPropertyExpr propertyExpr = (SQLPropertyExpr) where;
            tableInfoList.removeIf(item -> StringUtils.equalsIgnoreCase(propertyExpr.getOwner().toString(),
                    item.getAlias()) && StringUtils.equalsIgnoreCase(propertyExpr.getName(), config.getFieldName()));
        }

    }

    // 根据表信息拼接where
    private SQLBinaryOpExpr joinConditionSqlByTableList(List<TableInfo> tableInfoList, FieldConditionConfig config) {
        SQLBinaryOpExpr allCondition = new SQLBinaryOpExpr();
        for (int i = 0; i < tableInfoList.size(); i++) {
            TableInfo tableInfo = tableInfoList.get(i);

            SQLBinaryOpExpr fieldCondition = getFieldCondition(tableInfo, config);

            // 最后一个且非首个设为右条件
            if (i > 0 && i == tableInfoList.size() - 1) {
                allCondition.setOperator(SQLBinaryOperator.BooleanAnd);
                allCondition.setRight(fieldCondition);
                break;
            }

            // 只有一个table直接返回当前条件
            if (tableInfoList.size() == 1) {
                return fieldCondition;
            }

            if (Objects.isNull(allCondition.getLeft())) {
                allCondition.setLeft(fieldCondition);
                break;
            }

            allCondition.setLeft(and(allCondition.getLeft(), fieldCondition));
        }
        return allCondition;
    }

    // 根据表信息获得字段条件
    private SQLBinaryOpExpr getFieldCondition(TableInfo tableInfo, FieldConditionConfig config) {
        SQLBinaryOpExpr expr = new SQLBinaryOpExpr(DB_TYPE);

        expr.setOperator(config.getSqlBinaryOperator());
        expr.setLeft(StrUtil.isBlank(tableInfo.getAlias())
                ? new SQLIdentifierExpr(config.getFieldName())
                : new SQLPropertyExpr(tableInfo.getAlias(), config.getFieldName()));
        expr.setRight(config.getValueSqlExpr());
        return expr;
    }

    private SQLBinaryOpExpr and(SQLExpr left, SQLExpr right) {
        SQLBinaryOpExpr expr = new SQLBinaryOpExpr(DB_TYPE);
        expr.setLeft(left);
        expr.setOperator(SQLBinaryOperator.BooleanAnd);
        expr.setRight(right);
        return expr;
    }


    @Override
    public boolean visit(SQLUnionQueryTableSource x) {
        x.getUnion().accept(this);
        return super.visit(x);
    }

    @Override
    public boolean visit(SQLSelectItem x) {
        // 查询条件中的子查询项
        x.getExpr().accept(this);
        return super.visit(x);
    }

    @Override
    public boolean visit(SQLQueryExpr x) {
        x.getSubQuery().getQueryBlock().accept(this);
        return super.visit(x);
    }

    @Override
    public boolean visit(SQLUnionQuery x) {
        x.getLeft().accept(this);
        x.getRight().accept(this);
        return super.visit(x);
    }

    @Override
    public boolean visit(SQLSubqueryTableSource x) {
        x.getSelect().getQueryBlock().accept(this);
        return super.visit(x);
    }

    @Override
    public boolean visit(SQLJoinTableSource x) {
        x.getLeft().accept(this);
        x.getRight().accept(this);
        return super.visit(x);
    }

}
